package MyClustering;

/**
 * Created by dyq19921114 on 2015/11/3.
 */

import hep.aida.IHistogram;
import hep.aida.IHistogram1D;
import hep.aida.IHistogram2D;
import hep.aida.ref.Converter;
import hep.aida.ref.Histogram1D;
import hep.aida.ref.Histogram2D;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.*;
import java.util.List;


public class Clustering {
    private Map<Integer, DataPoint> m_PointsInform;   //原始数据
    private File m_file;
    private String m_filePath;
    private String m_fileName;
    private double m_maxX;      //求所有数据点的最大的x和y值，为了方便后续在640 * 480的图像上画点
    private double m_maxY;
    private double m_maxRho;
    private double m_minRho;
    private double m_maxDelta;
    private double m_minDelta;
    private double m_distance[][];  //坐标点两两间的距离
    private double dcPosition;

    public void setDc(double dc) {
        this.dc = dc;
    }

    private double dc;
    private double thRho;
    private double thDelta;

    public Clustering() {
        m_PointsInform = new HashMap<Integer, DataPoint>();
        m_fileName = "Compound";
        m_filePath = String.format(".%1$sDemo2%1$sdata%1$s%2$s.txt",
                File.separator, m_fileName);

        m_file = new File(m_filePath);
        m_maxX = 0.0;
        m_maxY = 0.0;
        m_maxRho = 0.0;
        m_minRho = 0.0;
        m_maxDelta = 0.0;
        dcPosition = 0.02;
    }

    public Map<Integer, DataPoint> getPtInforms() {
        return m_PointsInform;
    }

    public void readData() {
        String[] rawData = new String[3];  //将读到的每一行分成两个字符串，分别表示每个点的x和y的坐标
        //目前只考虑了二维空间的聚类，后面再加

        try {
            BufferedReader br = new BufferedReader(new FileReader(m_file));//构造一个BufferedReader类来读取文件
            String s = null;
            int key = 0;
            while ((s = br.readLine()) != null)  //使用readLine方法，一次读一行,注意这里读到最后一行后s里存的是""，依然可以进入循环？？
            {
                DataPoint tmpPoint = new DataPoint();
                rawData = s.split("\\,");
                tmpPoint.x = Double.parseDouble(rawData[0]);
                tmpPoint.y = Double.parseDouble(rawData[1]);
                tmpPoint.label = Integer.parseInt(rawData[2]);

                m_PointsInform.put(key, tmpPoint);
                if (tmpPoint.x > m_maxX)
                    m_maxX = tmpPoint.x;
                if (tmpPoint.y > m_maxY)
                    m_maxY = tmpPoint.y;
                key++;
            }
            br.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

        m_distance = new double[m_PointsInform.size()][m_PointsInform.size()];
    }

    public double getDistance(DataPoint pt1, DataPoint pt2) {
        double temp = (pt2.x - pt1.x) * (pt2.x - pt1.x) + (pt2.y - pt1.y) * (pt2.y - pt1.y);

        //这里Math.pow()算出来只能是int型，就会出现误差
        // int temp = (int)Math.pow((pt2.x - pt1.x),2) + (int)Math.pow((pt2.y - pt1.y),2);
        double dist = Math.sqrt(temp);
        return dist;
    }

    /*
     *这里截断距离dc的确定是根据python版的代码思路，将两两之间的距离进行排序，
     *位于前dcPosition位置的距离即为dc的值，可能还有其他方法算dc，如何选择dc才具有一般性
    */
    public double getDc() {
        int nSamples = m_PointsInform.size();
        ArrayList<Double> distToBeSorted = new ArrayList<Double>();
        for (int begin = 0; begin < nSamples - 1; begin++) {
            for (int end = begin + 1; end < nSamples; end++) {
                if (begin == end)
                    continue;
                double dist = getDistance(m_PointsInform.get(begin), m_PointsInform.get(end));
                m_distance[begin][end] = dist;
                m_distance[end][begin] = dist;
                distToBeSorted.add(dist);
            }
        }

        Collections.sort(distToBeSorted);
        int position = (int) (distToBeSorted.size() * dcPosition);

        IHistogram1D h1 = new Histogram1D("dist",20, 0, 40);
        for (Double d : distToBeSorted) {
            h1.fill(d);
        }
        System.out.println(new Converter().toString(h1));

        System.out.println("dc的值为：" + distToBeSorted.get(position));
        return distToBeSorted.get(position);
    }

    public void getLocalDensity(double dc)  //计算局部密度rho
    {
        int nSamples = m_PointsInform.size();
        for (int begin = 0; begin < nSamples - 1; begin++) {
            for (int end = begin + 1; end < nSamples; end++) {
                //以下是两种计算局部密度的方法
                if (getDistance(m_PointsInform.get(begin), m_PointsInform.get(end)) < dc) {
                    m_PointsInform.get(begin).rho++;
                    m_PointsInform.get(end).rho++;
                }
                /*m_PointsInform.get(begin).rho += Math.exp(-(m_distance[begin][end]/dc)*(m_distance[begin][end]/dc));
                m_PointsInform.get(end).rho += Math.exp(-(m_distance[begin][end]/dc)*(m_distance[begin][end]/dc));*/
            }
        }

        double avrOfRho = 0;
        for (int i = 0; i < m_PointsInform.size(); i++) {
            avrOfRho += m_PointsInform.get(i).rho;
        }
        avrOfRho /= m_PointsInform.size();

        System.out.println("平均密度占数据点总数的" + (double) avrOfRho / m_PointsInform.size());
    }

    public void getDistanceToHigherDensity()  //求每个点的delta，即每个点到具有更高密度的点的最小距离
    {
        List<Map.Entry<Integer,Double>> sortIndex;
        Map<Integer,Double> tempMap = new HashMap<>();
        for (Map.Entry<Integer, DataPoint> kv: m_PointsInform.entrySet()){
            tempMap.put(kv.getKey(),kv.getValue().rho);
        }
        sortIndex = new ArrayList<Map.Entry<Integer,Double>>(tempMap.entrySet());

        Collections.sort(sortIndex,
                new Comparator<Map.Entry<Integer, Double>>() {
                    @Override
                    public int compare(Map.Entry<Integer, Double> o1, Map.Entry<Integer, Double> o2) {
                        return o1.getValue().compareTo(o2.getValue());
                    }}
        );

        List<Integer> index = new ArrayList<Integer>();
        for (Map.Entry<Integer,Double> id : sortIndex){
            index.add(id.getKey());
        }

        int nSamples = index.size();
        int m, n;
        for (int i = 0; i < nSamples - 1; i++){
            double dist = 10000;
            m = index.get(i);
            for (int j = i + 1; j < nSamples; j++){
                n = index.get(j);
                if (m_PointsInform.get(n).rho > m_PointsInform.get(m).rho) {
                    double tmpDist = m_distance[m][n];
                    dist = tmpDist < dist ? tmpDist : dist;
                }
            }
            m_PointsInform.get(m).delta = dist;
        }

        //单独处理最后一个点，即最大的Rou值点；
        int i = index.get(index.size() - 1);
        double dist = 0;
        for (int j = 0; j < nSamples; j++) {
            double tmpDist = m_distance[i][j];
            dist = tmpDist > dist ? tmpDist : dist;
        }
        m_PointsInform.get(i).delta = dist;
    }

    public void findClusterCenter() {
        //判断聚类中心时的阈值，需要将所有点的rho和delta值分别排序
        thRho = 0.0;     //thRho = 0.6 * (rho(max) - rho(min)) + rho(min)
        thDelta = 0.0;    //thDelta = 0.2 * (delta(max) - delta(min)) + delta(min)

        ArrayList<Double> rhoBeOrdered = new ArrayList<Double>();
        ArrayList<Double> deltaBeOrdered = new ArrayList<Double>();

        for (int i = 0; i < m_PointsInform.size(); i++) {
            rhoBeOrdered.add(m_PointsInform.get(i).rho);
            deltaBeOrdered.add(m_PointsInform.get(i).delta);
        }

        Collections.sort(rhoBeOrdered);
        Collections.sort(deltaBeOrdered);
        m_maxRho = rhoBeOrdered.get(rhoBeOrdered.size() - 1);
        m_minRho = rhoBeOrdered.get(0);
        m_maxDelta = deltaBeOrdered.get(deltaBeOrdered.size() - 1);
        m_minDelta = deltaBeOrdered.get(0);

        IHistogram2D h2 = new Histogram2D("rou&delta",20, 0, 100, 20, 0, 40);
        for (int i = 0; i < m_PointsInform.size(); i++) {
            h2.fill(m_PointsInform.get(i).rho, m_PointsInform.get(i).delta);
        }
        System.out.println(new Converter().toString(h2));

        IHistogram1D h3 = new Histogram1D("gama=rou*delta",20,0,200);
        for (int i = 0; i < m_PointsInform.size(); i++) {
            h3.fill(m_PointsInform.get(i).rho * m_PointsInform.get(i).delta);
        }
        System.out.println(new Converter().toString(h3));

        //以下的0.8和0.2是个经验值，目前不知道怎么确定
        thRho = 0.5 * (m_maxRho - m_minRho) + m_minRho;
        thDelta = 0.08 * (m_maxDelta - m_minDelta) + m_minDelta;
        thRho = 5.1;    //这个参数仅仅针对Compound.txt数据集
        thDelta = 3.0;
//        double[] mean_std = mean_std(deltaBeOrdered);
//        thDelta = mean_std[0] + 3.0 * mean_std[1];
//        mean_std = mean_std(rhoBeOrdered);
//        thRho = mean_std[0] - 2.0 * mean_std[1];

        int centerType = 1;    //分类类别，从1开始，因为ClusterResult初始化默认为0
        for (int j = 0; j < m_PointsInform.size(); j++) {
            if (m_PointsInform.get(j).rho > thRho && m_PointsInform.get(j).delta > thDelta) {
                //第j个点可判定为一个聚类中心
                m_PointsInform.get(j).label = centerType;
                m_PointsInform.get(j).isClassCenter = true;

                System.out.println(String.format("id=%d; x=%.2f; y=%.2f; rho=%.3f; delta=%.3f",j,
                        m_PointsInform.get(j).x,m_PointsInform.get(j).y,
                        m_PointsInform.get(j).rho,m_PointsInform.get(j).delta));

                centerType++;
            }
        }
        System.out.println("共分成" + (centerType - 1) + "类");
    }

    /*官方介绍的方法是将rho进行降序排列，点i的label值是rho比i大的点中距离
    * i最近的点的label值，但是效果并不好，因为rho排在较前的不一定是聚类中心，
    * 所以可能并没有分类完成，其label值为0，此时i则分类失败
    * 现将其改成按rho*delta进行降序排列，后面思路一样
    */
    public void classifyOtherDataPoint(){
        Map<Integer,Double> mapRhoMulDelta = new HashMap<Integer,Double>();
        for (int i = 0; i < m_PointsInform.size(); i++) {
            mapRhoMulDelta.put(i, m_PointsInform.get(i).rho * m_PointsInform.get(i).delta);
        }
        //infoIds的value值是rho*delta降序排序的值，而infoIds值是每个点在原始数据集中的下标值
        List<Map.Entry<Integer,Double>> infoIds =
                new ArrayList<Map.Entry<Integer,Double>>(mapRhoMulDelta.entrySet());
        Collections.sort(infoIds,
                new Comparator<Map.Entry<Integer, Double>>() {
                    @Override
                    public int compare(Map.Entry<Integer, Double> o1, Map.Entry<Integer, Double> o2) {
                        return o2.getValue().compareTo(o1.getValue());
                    }
                }
        );

        for(int j = 1; j < m_PointsInform.size(); j++){
            int index = infoIds.get(j).getKey();
            int indexMin = 0;
            double minDist = 10000.0;
            if(m_PointsInform.get(index).label == 0){
                for(int k = 0; k < j; k++){
                    if(m_distance[index][infoIds.get(k).getKey()] < minDist){
                        minDist = m_distance[index][infoIds.get(k).getKey()];
                        indexMin = infoIds.get(k).getKey();
                    }
                }
                m_PointsInform.get(index).label = m_PointsInform.get(indexMin).label;
            }
        }
    }

    public Color getRandomColor() {
        Random random = new Random();
        int red = random.nextInt(255);
        int blue = random.nextInt(255);
        int green = random.nextInt(255);
        Color randomColor = new Color(red, green, blue);
        return randomColor;
    }

    public void drawImage() {
        ArrayList<Color> randomColorList = new ArrayList<Color>();
        for (int i = 0; i < m_PointsInform.size(); i++) {
            randomColorList.add(i, getRandomColor());
        }

        BufferedImage bImage = new BufferedImage(640, 480, BufferedImage.TYPE_INT_RGB);   //分类结果图
        int red = 0;
        int green = 0;
        int blue = 0;
        int rgb = 0;
        for (int row = 0; row < bImage.getHeight(); row++)
            for (int col = 0; col < bImage.getWidth(); col++) {
                Object data = bImage.getRaster().getDataElements(col, row, null);
                red = bImage.getColorModel().getRed(data);
                blue = bImage.getColorModel().getBlue(data);
                green = bImage.getColorModel().getGreen(data);
                red = 255;
                blue = 255;
                green = 255;

                rgb = (red * 256 + green) * 256 + blue;
                if (rgb > 8388608) {
                    rgb -= 16777216;
                }
                bImage.setRGB(col, row, rgb);
            }

        int rateX = (int) (640 / m_maxX);
        int rateY = (int) (480 / m_maxY);
        Graphics2D g = (Graphics2D) bImage.getGraphics();
        int size;
        for (int k = 0; k < m_PointsInform.size(); k++) {
            int x = (int) (m_PointsInform.get(k).x * rateX);
            int y = (int) (480 - (m_PointsInform.get(k).y * rateY));
            if (m_PointsInform.get(k).isClassCenter) {
                size = 10;
            } else {
                size = 4;
            }

            g.drawOval(x, y, size, size);
            g.setColor(randomColorList.get(m_PointsInform.get(k).label));
            g.fillOval(x, y, size, size);
        }
        g.setColor(Color.black);
        g.drawString(String.format("pos = %f; dc = %.3f; thRho = %.3f; thDelta = %.3f",
                this.dcPosition, this.dc, this.thRho, this.thDelta), 5, 460);

        BufferedImage bImageRhoAndDelta = new BufferedImage(640, 480, BufferedImage.TYPE_INT_RGB); //rho&delta图
        for (int row = 0; row < bImageRhoAndDelta.getHeight(); row++)
            for (int col = 0; col < bImageRhoAndDelta.getWidth(); col++) {
                Object data = bImageRhoAndDelta.getRaster().getDataElements(col, row, null);
                red = bImageRhoAndDelta.getColorModel().getRed(data);
                green = bImageRhoAndDelta.getColorModel().getGreen(data);
                red = 255;
                blue = 255;
                green = 255;
                rgb = (red * 256 + green) * 256 + blue;
                if (rgb > 8388608) {
                    rgb -= 16777216;
                }
                bImageRhoAndDelta.setRGB(col, row, rgb);
            }

        rateX = (int) (608 / m_maxRho);
        rateY = (int) (456 / m_maxDelta);
        Graphics2D gRD = (Graphics2D) bImageRhoAndDelta.getGraphics();
        Color color;
        for (int k = 0; k < m_PointsInform.size(); k++) {
            int x = (int) (m_PointsInform.get(k).rho * rateX);
            int y = (int) (480 - (m_PointsInform.get(k).delta * rateY));
            if (m_PointsInform.get(k).isClassCenter) {
                color = Color.RED;
            } else {
                color = Color.BLUE;
            }

            gRD.drawOval(x, y, 4, 4);
            gRD.setColor(color);
            gRD.fillOval(x, y, 4, 4);
        }

        BufferedImage bImageRhoMulDelta = new BufferedImage(3200 , 480, BufferedImage.TYPE_INT_RGB);   //rho*delta图
        for (int row = 0; row < bImageRhoMulDelta.getHeight(); row++)
            for (int col = 0; col < bImageRhoMulDelta.getWidth(); col++) {
                Object data = bImageRhoMulDelta.getRaster().getDataElements(col, row, null);
                red = bImageRhoMulDelta.getColorModel().getRed(data);
                blue = bImageRhoMulDelta.getColorModel().getBlue(data);
                green = bImageRhoMulDelta.getColorModel().getGreen(data);
                red = 255;
                blue = 255;
                green = 255;
                rgb = (red * 256 + green) * 256 + blue;
                if (rgb > 8388608) {
                    rgb -= 16777216;
                }
                bImageRhoMulDelta.setRGB(col, row, rgb);
            }
        Graphics2D gRhoMulDelta = (Graphics2D) bImageRhoMulDelta.getGraphics();
        ArrayList<Double> rhoMulDelta = new ArrayList<Double>();
        for (int i = 0; i < m_PointsInform.size(); i++){
            double normRho = (double)((m_PointsInform.get(i).rho - m_minRho) / (m_maxRho - m_minRho));
            double normDelta = (double)((m_PointsInform.get(i).delta - m_minDelta) / (m_maxDelta - m_minDelta));
            rhoMulDelta.add(normRho * normDelta);
        }
        Collections.sort(rhoMulDelta);
        for (int k = 0; k < m_PointsInform.size(); k++){
            int x = k;
            int y = (int)(480 - (rhoMulDelta.get(k)*100 ));

            gRhoMulDelta.drawOval(x, y, 4, 4);
            gRhoMulDelta.setColor(Color.BLACK);
            gRhoMulDelta.fillOval(x, y, 4, 4);
        }

        try {
            String savePath = String.format(".%1$sDemo2%1$sresult%1$s%2$s.jpg",
                    File.separator, m_fileName);

            ImageIO.write(bImage, "jpg", new File(savePath));

            savePath = String.format(".%1$sDemo2%1$sresult%1$s%2$s.jpg",
                    File.separator,m_fileName+"rAndd");
            ImageIO.write(bImageRhoAndDelta,"jpg", new File(savePath));

            savePath = String.format(".%1$sDemo2%1$sresult%1$s%2$s.jpg",
                    File.separator,m_fileName+"rMuld");
            ImageIO.write(bImageRhoMulDelta,"jpg", new File(savePath));

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * get Mean and std of the List values.
     *
     * @param <T>    the type parameter
     * @param values the values
     * @return the double [ ]
     */
    /*public static <T extends Number> double[] mean_std(List<T> values) {
        //using this equation : Var(x) = E(x^2)-(E(x))^2
        int len = values.size();
        if (len > 0) {
            double m = 0;
            double s = 0;
            double v;
            for (T d : values) {
                v = d.doubleValue();
                m += v;
                s += v * v;
            }
            double mean = m / len;

            return new double[]{mean, Math.sqrt(s / len - mean * mean)};
        } else
            return null;
    }*/

    public static void main(String[] args) {

        Clustering myCluster = new Clustering();

        System.out.println("Start:" + myCluster.m_fileName + ".txt");

        myCluster.readData();
        System.out.println("数据点个数：" + myCluster.m_PointsInform.size());
        Map<Integer, DataPoint> myData = myCluster.getPtInforms();

        double dc = myCluster.getDc();
        dc = 3.0;    //目前这一参数仅仅在Compound.txt数据集上
        myCluster.setDc(dc);
        myCluster.getLocalDensity(dc);
        myCluster.getDistanceToHigherDensity();

        myCluster.findClusterCenter();
        myCluster.classifyOtherDataPoint();

        myCluster.drawImage();
    }
}

